import * as THREE from '../../build/three.module.js';
import Stats from '../../jsm/libs/stats.module.js'
import { GUI } from '../../jsm/libs/dat.gui.module.js';

import { DragControls } from '../../jsm/controls/DragControls.js';
import { OrbitControls } from '../../jsm/controls/OrbitControls.js';
import { TransformControls } from '../../jsm/controls/TransformControls.js';

String.prototype.format = function() {
  let str = this;
  
  for(let i = 0; i < arguments.length; i += 1) {
    str = str.replace(`{${i}}`, arguments[i]);
  }

  return str;
}

const splineHelperObjects = [],
      point = new THREE.Vector3(),
      splines = {},
      ARC_SEGMENTS = 200;
let positions = [];
let splinePointsLength = 4; // 当前相关点数量 
const gmt = new THREE.BoxBufferGeometry(20, 20, 20);

// 添加点模型对象
const addSplineObject = (position) => {
  const mtl = new THREE.MeshLambertMaterial({ color: Math.random() * 0xffffff });
  const mesh = new THREE.Mesh(gmt, mtl);
  if (position) {
    mesh.position.copy(position);
  } else {
    mesh.position.x = Math.random() * 1000 - 500;
    mesh.position.y = Math.random() * 600;
    mesh.position.z = Math.random() * 800 - 400;
  }
  mesh.castShadow = true; // 启用阴影贴图
  mesh.receiveShadow = true; // 是否启用材质阴影
  scene.add(mesh);
  splineHelperObjects.push(mesh);
  return mesh;
}

// 更新样条外部线
const updateSplineOutline = () => {
  for(const key in splines) {
    const spline = splines[key];

    const splineMesh = spline.mesh;
    const position = splineMesh.geometry.attributes.position;

    for(let i = 0; i < ARC_SEGMENTS; i += 1) {
      const t = i / (ARC_SEGMENTS - 1);
      spline.getPoint(t, point);
      position.setXYZ(i, point.x, point.y, point.z);
    }

    position.needsUpdate = true; // 启动更新
  }
}

// 添加点处理函数
const addPoint = () => {
  splinePointsLength++;
  positions.push( addSplineObject().position );
  updateSplineOutline();
}

// 删除一个点
const removePoint = () => {
  if (splinePointsLength <= 4) {
    return;
  }
  splinePointsLength--;
  positions.pop();
  scene.remove(splineHelperObjects.pop());
  updateSplineOutline();
}

// 导出样条先坐标代码
const exportSpline = () => {
  const strPlace = [];
  for(let i = 0; i < splinePointsLength; i += 1) {
    const p = splineHelperObjects[i].position;
    strPlace.push('new THREE.Vector3({0}, {1}, {2})'.format(p.x, p.y, p.z))
  }
  const code = `[${strPlace.join(',\n\t')}]`;
  prompt('复制粘贴代码', code);
}

const params = {
  uniform: true,
  tension: .5, // 紧张度
  centripetal: true, // 向心的
  chordal: true, // 弦的
  添加点: addPoint, // 添加点
  删除点: removePoint, // 删除点
  导出样条代码: exportSpline
}

const scene = new THREE.Scene();
scene.background = new THREE.Color(0xf0f0f0);

const camera = new THREE.PerspectiveCamera(65, innerWidth / innerHeight, 1, 10000);
camera.position.set(0, 250, 1000);
scene.add(camera);

scene.add( new THREE.AmbientLight(0xf0f0f0) );

// 聚光灯
const light = new THREE.SpotLight( 0xffffff, 1.5 );
light.position.set( 0, 1500, 200 );
light.castShadow = true;
light.shadow = new THREE.LightShadow( new THREE.PerspectiveCamera( 70, 1, 200, 2000 ) );
light.shadow.bias = - 0.000222;
light.shadow.mapSize.width = 1024;
light.shadow.mapSize.height = 1024;
scene.add( light );

const planeGmt = new THREE.PlaneBufferGeometry(2000, 2000);
planeGmt.rotateX(-Math.PI / 2);
const planeMtl = new THREE.ShadowMaterial({ opacity: .2 });
const plane = new THREE.Mesh(planeGmt, planeMtl);
plane.position.y = -200;
plane.receiveShadow = true; // 接收阴影
scene.add(plane);

const helper = new THREE.GridHelper(2000, 100);
helper.position.y = -199;
helper.material.opacity = .25;
helper.material.transparent = true;
scene.add(helper);

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(devicePixelRatio);
renderer.setSize(innerWidth, innerHeight);
renderer.shadowMap.enabled = true;
document.body.appendChild(renderer.domElement);

/**
 * 曲线
 */
for(let i = 0; i < splinePointsLength; i += 1) {
  addSplineObject( positions[i] );
}

positions = [];

for(let i = 0; i < splinePointsLength; i += 1) {
  positions.push( splineHelperObjects[i].position );
}

const lineGmt = new THREE.BufferGeometry();
lineGmt.setAttribute('position', new THREE.BufferAttribute( new Float32Array(ARC_SEGMENTS * 3), 3 ));

let curve = new THREE.CatmullRomCurve3(positions);
curve.curveType = 'catmullrom';
curve.mesh = new THREE.Line(lineGmt.clone(), new THREE.LineBasicMaterial({
  color: 0xff0000,
  opacity: .35
}));
curve.mesh.castShadow = true;
splines.uniform = curve;

curve = new THREE.CatmullRomCurve3(positions);
curve.curveType = 'centripetal';
curve.mesh = new THREE.Line(lineGmt.clone(), new THREE.LineBasicMaterial({
  color: 0x00ff00,
  opacity: .35
}));
curve.castShadow = true;
splines.centripetal = curve;

curve = new THREE.CatmullRomCurve3(positions);
curve.curveType = 'chordal';
curve.mesh = new THREE.Line(lineGmt.clone(), new THREE.LineBasicMaterial({
  color: 0x0000ff,
  opacity: .35
}));
curve.castShadow = true;
splines.chordal = curve;

for(const k in splines) {
  const spline = splines[k];
  scene.add(spline.mesh);
}

// 初始化
const load = (new_positions) => {
  while (new_positions.length > positions.length) {
    addPoint();
  }

  while (new_positions.length < positions.length) {
    removePoint();
  }

  for(let i = 0; i < positions.length; i += 1) {
    positions[i].copy(new_positions[i]);
  }

  updateSplineOutline();
}

load([
  new THREE.Vector3( 289.76843686945404, 452.51481137238443, 56.10018915737797 ),
  new THREE.Vector3( - 53.56300074753207, 171.49711742836848, - 14.495472686253045 ),
  new THREE.Vector3( - 91.40118730204415, 176.4306956436485, - 6.958271935582161 ),
  new THREE.Vector3( - 383.785318791128, 491.1365363371675, 47.869296953772746 )
])

// Stats
const state = new Stats();
document.body.appendChild(state.dom);

// GUI
const gui = new GUI();
gui.add(params, 'uniform');
gui.add(params, 'tension', 0, 1).step(.01).onChange(val => {
  splines.uniform.tension = val;
  updateSplineOutline();
})
gui.add(params, 'centripetal');
gui.add(params, 'chordal');
gui.add(params, '添加点');
gui.add(params, '删除点');
gui.add(params, '导出样条代码');
gui.open();

// 控制器
let hiding, transformControl;
const cancelHideTransform = () => {
  if (hiding) clearTimeout(hiding);
}
const hideTransform = () => {
  hiding = setTimeout(() => {
    transformControl.detach(transformControl.object);
  }, 2500);
}
const delayHideTransform = () => {
  cancelHideTransform();
  hideTransform();
}

// 轨道控制器
const control = new OrbitControls(camera, renderer.domElement);
control.damping = .2;
control.addEventListener('start', () => {
  cancelHideTransform();
});
control.addEventListener('end', () => {
  delayHideTransform();
})

const render = () => {
  splines.uniform.mesh.visible = params.uniform;
  splines.centripetal.mesh.visible = params.centripetal;
  splines.chordal.mesh.visible = params.chordal;
  renderer.render(scene, camera);
}
control.addEventListener('change', render);

// 变化控制器
transformControl = new TransformControls(camera, renderer.domElement);
transformControl.addEventListener('change', render);
transformControl.addEventListener('dragging-changed', e => {
  control.enabled = !e.value;
})
scene.add(transformControl);
transformControl.addEventListener( 'change', () => {
  cancelHideTransform();
});
transformControl.addEventListener( 'mouseDown', () => {
  cancelHideTransform();
});
transformControl.addEventListener( 'mouseUp', () => {
  delayHideTransform();
});
transformControl.addEventListener( 'objectChange', () => {
  updateSplineOutline();
});

// 拖拽控制器
const dragControls = new DragControls(splineHelperObjects, camera, renderer.domElement);
dragControls.enabled = false;
dragControls.addEventListener('hoveron', e => {
  transformControl.attach(e.object);
  cancelHideTransform();
})
dragControls.addEventListener('hoveroff', () => {
  delayHideTransform();
})

const animate = () => {
  requestAnimationFrame(animate);
  state.update();
  render();
}
animate();

const handleWindowResize = () => {
  camera.aspect = innerWidth / innerHeight;
  camera.updateProjectionMatrix();

  renderer.setSize(innerWidth, innerHeight);
}
window.addEventListener('resize', handleWindowResize, false);